/*
 * author	: calvin
 * email	: calvinwilliams@163.com
 *
 * Licensed under the LGPL v2.1, see the file LICENSE in base directory.
 */

#include "cdbc.h"

#include "sqlite3.h"

#define _DEBUG		0

#if _DEBUG
#define _TRACE(_format_,...) { printf( "TRACE - %s:%d:%s - "_format_"\n" , __FILE__,__LINE__,__FUNCTION__ , ##__VA_ARGS__ ); fflush(stdout); }
#else
#define _TRACE(_format_,...)
#endif

#define SQLITE3_ROW_COUNT_INIT		1
#define SQLITE3_ROW_COUNT_INCREASE	100

struct DatabaseConnection
{
	sqlite3		*sqlite3_db ;
} ;

DLLEXPORT funcConnectToDatabase ConnectToDatabase ;
DLLEXPORT funcDisconnectFromDatabase DisconnectFromDatabase ;
DLLEXPORT funcExecuteSql ExecuteSql ;

struct DatabaseConnection *ConnectToDatabase( char *db_host , int db_port , char *db_user , char *db_pass , char *db_name )
{
	struct DatabaseConnection	*db_conn = NULL ;
	int				nret = 0 ;
	
	db_conn = (struct DatabaseConnection *)malloc( sizeof(struct DatabaseConnection) ) ;
	if( db_conn == NULL )
	{
		DBCSetLastErrno( CDBC_ERROR_ALLOC );
		return NULL;
	}
	memset( db_conn , 0x00 , sizeof(struct DatabaseConnection) );
	
	nret = sqlite3_open( db_name , & (db_conn->sqlite3_db) ) ;
	if( nret != SQLITE_OK )
	{
		DisconnectFromDatabase( & db_conn );
		DBCSetLastErrno( CDBC_ERROR_CONNECT );
		return NULL;
	}
	
	return db_conn;
}

void DisconnectFromDatabase( struct DatabaseConnection **db_conn )
{
	if( db_conn == NULL )
	{
		DBCSetLastErrno( CDBC_ERROR_PARAMETER );
		return;
	}
	
	if( (*db_conn) )
	{
		if( (*db_conn)->sqlite3_db )
		{
			sqlite3_close( (*db_conn)->sqlite3_db ); (*db_conn)->sqlite3_db = NULL ;
		}
		
		free( (*db_conn) ); (*db_conn) = NULL ;
	}
	
	return;
}

static enum FieldType ConvertSqliteFieldType( int type )
{
	if( type == SQLITE_INTEGER )
		return CDBC_FIELDTYPE_INT64;
	else if( type == SQLITE_FLOAT )
		return CDBC_FIELDTYPE_DOUBLE;
	else if( type == SQLITE_TEXT )
		return CDBC_FIELDTYPE_VARCHAR;
	else
		return CDBC_FIELDTYPE_OTHER;
}

#if 0
static int ConvertCdbcFieldType( enum FieldType type )
{
	if( type == CDBC_FIELDTYPE_INT8 )
		return SQLITE_INTEGER;
	else if( type == CDBC_FIELDTYPE_INT16 )
		return SQLITE_INTEGER;
	else if( type == CDBC_FIELDTYPE_INT32 )
		return SQLITE_INTEGER;
	else if( type == CDBC_FIELDTYPE_INT64 )
		return SQLITE_INTEGER;
	else if( type == CDBC_FIELDTYPE_FLOAT )
		return SQLITE_FLOAT;
	else if( type == CDBC_FIELDTYPE_DOUBLE )
		return SQLITE_FLOAT;
	else if( type == CDBC_FIELDTYPE_DECIMAL )
		return SQLITE_FLOAT;
	else if( type == CDBC_FIELDTYPE_CHAR )
		return SQLITE_TEXT;
	else if( type == CDBC_FIELDTYPE_VARCHAR )
		return SQLITE_TEXT;
	else if( type == CDBC_FIELDTYPE_DATE )
		return SQLITE_TEXT;
	else if( type == CDBC_FIELDTYPE_TIME )
		return SQLITE_TEXT;
	else if( type == CDBC_FIELDTYPE_DATETIME )
		return SQLITE_TEXT;
	else if( type == CDBC_FIELDTYPE_TIMESTAMP )
		return SQLITE_TEXT;
	else
		return SQLITE_TEXT;
}
#endif

void ExecuteSql( struct DatabaseConnection *db_conn , char *sql , struct FieldBind *binds_array , int binds_array_length , int *row_count , int *col_count , struct FieldInfo **query_field_set , char ***query_result_set , int *affected_count )
{
	sqlite3_stmt		*stmt = NULL ;
	int			bind_parameter_count ;
	int			binds_array_no ;
	struct FieldBind	*binds_array_offsetptr = NULL ;
	int			sqlite3_affected_count ;
	size_t			alloc_size ;
	int			sqlite3_col_count , sqlite3_col_index ;
	int			sqlite3_row_count ;
	int			sqlite3_page_row_index , sqlite3_page_row_count ;
	size_t			set_index ;
	struct FieldInfo	*sqlite3_query_field_set = NULL ;
	char			**sqlite3_query_result_set = NULL ;
	char			*sqlite3_field_name = NULL ;
	char			*sqlite3_field_value = NULL ;
	int			nret = 0 ;
	
	DBCFreeSqlResult( query_field_set , query_result_set );
	
	nret = sqlite3_prepare( db_conn->sqlite3_db , sql , -1 , & stmt , NULL ) ;
	_TRACE( "sqlite3_prepare return[%d]" , nret )
	if( nret != SQLITE_OK )
	{
		DBCSetLastErrno( CDBC_ERROR_PREAPRE );
		DBCSetLastNativeErrno( sqlite3_errcode(db_conn->sqlite3_db) );
		DBCSetLastNativeError( (char*)sqlite3_errmsg(db_conn->sqlite3_db) );
		return;
	}
	
	bind_parameter_count = sqlite3_bind_parameter_count( stmt ) ;
	_TRACE( "sqlite3_bind_parameter_count return bind_parameter_count[%d]" , bind_parameter_count )
	if( bind_parameter_count > 0 && binds_array && binds_array_length > 0 )
	{
		int		n32 ;
		sqlite3_int64	n64 ;
		double		d64 ;
		
		if( bind_parameter_count != binds_array_length )
		{
			DBCSetLastErrno( CDBC_ERROR_BIND );
			DBCSetLastNativeErrno( sqlite3_errcode(db_conn->sqlite3_db) );
			DBCSetLastNativeError( (char*)sqlite3_errmsg(db_conn->sqlite3_db) );
			return;
		}
		
		for( binds_array_no = 1 , binds_array_offsetptr = binds_array ; binds_array_no <= binds_array_length ; binds_array_no++ , binds_array_offsetptr++ )
		{
			if( binds_array_offsetptr->buffer_type == CDBC_FIELDTYPE_INT16 )
			{
				n32 = (int) *(int16_t*)(binds_array_offsetptr->buffer) ;
				_TRACE( "bind sql param - buffer_type[%d] buffer[%d]" , binds_array_offsetptr->buffer_type , n32 )
				sqlite3_bind_int( stmt , binds_array_no , n32 );
			}
			else if( binds_array_offsetptr->buffer_type == CDBC_FIELDTYPE_INT32 )
			{
				n32 = (int) *(int32_t*)(binds_array_offsetptr->buffer) ;
				_TRACE( "bind sql param - buffer_type[%d] buffer[%d]" , binds_array_offsetptr->buffer_type , n32 )
				sqlite3_bind_int( stmt , binds_array_no , n32 );
			}
			else if( binds_array_offsetptr->buffer_type == CDBC_FIELDTYPE_INT64 )
			{
				n64 = (sqlite3_int64) *(int64_t*)(binds_array_offsetptr->buffer) ;
				_TRACE( "bind sql param - buffer_type[%d] buffer[%"PRIi64"]" , binds_array_offsetptr->buffer_type , (int64_t)n64 )
				sqlite3_bind_int( stmt , binds_array_no , n64 );
			}
			else if( binds_array_offsetptr->buffer_type == CDBC_FIELDTYPE_FLOAT )
			{
				d64 = (double) *(float*)(binds_array_offsetptr->buffer) ;
				_TRACE( "bind sql param - buffer_type[%d] buffer[%lf]" , binds_array_offsetptr->buffer_type , d64 )
				sqlite3_bind_double( stmt , binds_array_no , d64 );
			}
			else if( binds_array_offsetptr->buffer_type == CDBC_FIELDTYPE_DOUBLE )
			{
				d64 = *(double*)(binds_array_offsetptr->buffer) ;
				_TRACE( "bind sql param - buffer_type[%d] buffer[%lf]" , binds_array_offsetptr->buffer_type , d64 )
				sqlite3_bind_double( stmt , binds_array_no , d64 );
			}
			else if( binds_array_offsetptr->buffer_type == CDBC_FIELDTYPE_CHAR || binds_array_offsetptr->buffer_type == CDBC_FIELDTYPE_VARCHAR )
			{
				_TRACE( "bind sql param - buffer_type[%d] buffer[%s]" , binds_array_offsetptr->buffer_type , (char*)(binds_array_offsetptr->buffer) )
				sqlite3_bind_text( stmt , binds_array_no , binds_array_offsetptr->buffer , binds_array_offsetptr->buffer_length , NULL );
			}
			else if( binds_array_offsetptr->buffer_type == CDBC_FIELDTYPE_DATETIME || binds_array_offsetptr->buffer_type == CDBC_FIELDTYPE_TIMESTAMP )
			{
				struct tm	*p_tm = (struct tm *)(binds_array_offsetptr->buffer) ;
				char		datetime_buf[ 10+1+8 + 1] ;
				int		datetime_str_len ;
				
				memset( datetime_buf , 0x00 , sizeof(datetime_buf) );
				datetime_str_len = strftime( datetime_buf , sizeof(datetime_buf) , "%Y-%m-%d %H:%M:%S" , p_tm ) ;
				_TRACE( "bind sql param - buffer_type[%d] buffer[%s]" , binds_array_offsetptr->buffer_type , datetime_buf )
				sqlite3_bind_text( stmt , binds_array_no , datetime_buf , datetime_str_len , NULL );
			}
			else
			{
				_TRACE( "unknow cdbc_type[%d]" , binds_array_offsetptr->buffer_type )
				DBCSetLastErrno( CDBC_ERROR_BIND );
				DBCSetLastNativeErrno( 0 );
				DBCSetLastNativeError( "" );
				return;
			}
		}
	}
	else if( bind_parameter_count == 0 && binds_array == NULL && binds_array_length == 0 )
	{
		;
	}
	else
	{
		DBCSetLastErrno( CDBC_ERROR_BIND );
		DBCSetLastNativeErrno( 0 );
		DBCSetLastNativeError( "" );
		return;
	}
	
	nret = sqlite3_step( stmt ) ;
	_TRACE( "sqlite3_step return[%d]" , nret )
	if( nret == SQLITE_OK || nret == SQLITE_DONE )
	{
		sqlite3_row_count = 0 ;
		sqlite3_col_count = 0 ;
		sqlite3_query_field_set = NULL ;
		sqlite3_query_result_set = NULL ;
		sqlite3_affected_count = sqlite3_changes( db_conn->sqlite3_db ) ;
		_TRACE( "sqlite3_changes return sqlite3_affected_count[%d]" , sqlite3_affected_count )
	}
	else if( nret == SQLITE_ROW )
	{
		sqlite3_row_count = 0 ;
		sqlite3_col_count = sqlite3_column_count( stmt ) ;
		_TRACE( "sqlite3_column_count return sqlite3_col_count[%d]" , sqlite3_col_count )
		if( sqlite3_col_count > 0 )
		{
			alloc_size = sizeof(struct FieldInfo) * (sqlite3_col_count+1) ;
			sqlite3_query_field_set = (struct FieldInfo *)malloc( alloc_size ) ;
			if( sqlite3_query_field_set == NULL )
			{
				sqlite3_finalize( stmt );
				DBCSetLastErrno( CDBC_ERROR_ALLOC );
				DBCSetLastNativeErrno( 0 );
				DBCSetLastNativeError( "" );
				return;
			}
			memset( sqlite3_query_field_set , 0x00 , alloc_size );
			
			for( sqlite3_col_index = 0 ; sqlite3_col_index < sqlite3_col_count ; sqlite3_col_index++ )
			{
				sqlite3_field_name = (char*)sqlite3_column_name( stmt , sqlite3_col_index ) ;
				sqlite3_query_field_set[sqlite3_col_index].field_name = strdup( sqlite3_field_name ) ;
				if( sqlite3_query_field_set[sqlite3_col_index].field_name == NULL )
				{
					free( sqlite3_query_field_set );
					sqlite3_finalize( stmt );
					DBCSetLastErrno( CDBC_ERROR_ALLOC );
					DBCSetLastNativeErrno( 0 );
					DBCSetLastNativeError( "" );
					return;
				}
				sqlite3_query_field_set[sqlite3_col_index].field_type = ConvertSqliteFieldType( sqlite3_column_type( stmt , sqlite3_col_index ) ) ;
				if( sqlite3_query_field_set[sqlite3_col_index].field_type == CDBC_FIELDTYPE_INVALID )
				{
					free( sqlite3_query_field_set );
					sqlite3_finalize( stmt );
					DBCSetLastErrno( CDBC_ERROR_FIELD_TYPE_NOT_SUPPORT );
					DBCSetLastNativeErrno( 0 );
					DBCSetLastNativeError( "" );
					return;
				}
				sqlite3_query_field_set[sqlite3_col_index].field_length = 0 ;
				sqlite3_query_field_set[sqlite3_col_index].field_decimal_length = 0 ;
				_TRACE( "  sqlite3_query_field_set .field_type[%d]->[%d] .field_name[%s] .field_length[%d] .field_decimal_length[%d]" , sqlite3_column_type(stmt,sqlite3_col_index),sqlite3_query_field_set[sqlite3_col_index].field_type , sqlite3_query_field_set[sqlite3_col_index].field_name , sqlite3_query_field_set[sqlite3_col_index].field_length , sqlite3_query_field_set[sqlite3_col_index].field_decimal_length )
			}
		}
		
		sqlite3_page_row_index = 1 ;
		sqlite3_page_row_count = 0 ;
		set_index = 0 ;
		for( ; ; )
		{
			_TRACE( "  sqlite3_row_index[%lu]" , (unsigned long)sqlite3_page_row_index )
			
			if( sqlite3_page_row_count == 0 )
			{
				sqlite3_page_row_count = SQLITE3_ROW_COUNT_INIT ;
				alloc_size = sizeof(char*) * (sqlite3_page_row_count*sqlite3_col_count) ;
				sqlite3_query_result_set = (char**)malloc( alloc_size ) ;
				if( sqlite3_query_result_set == NULL )
				{
					if( sqlite3_query_field_set )
						free( sqlite3_query_field_set );
					sqlite3_finalize( stmt );
					DBCSetLastErrno( CDBC_ERROR_ALLOC );
					DBCSetLastNativeErrno( 0 );
					DBCSetLastNativeError( "" );
					return;
				}
				memset( sqlite3_query_result_set , 0x00 , alloc_size );
			}
			
			if( sqlite3_row_count >= sqlite3_page_row_count )
			{
				char		**new_sqlite3_query_result_set = NULL ;
				
				sqlite3_page_row_count += SQLITE3_ROW_COUNT_INCREASE ;
				alloc_size = sizeof(char*) * (sqlite3_page_row_count*sqlite3_col_count) ;
				new_sqlite3_query_result_set = (char**)realloc( sqlite3_query_result_set , alloc_size ) ;
				if( new_sqlite3_query_result_set == NULL )
				{
					if( sqlite3_query_field_set )
						free( sqlite3_query_field_set );
					sqlite3_finalize( stmt );
					DBCSetLastErrno( CDBC_ERROR_ALLOC );
					DBCSetLastNativeErrno( 0 );
					DBCSetLastNativeError( "" );
					return;
				}
				sqlite3_query_result_set = new_sqlite3_query_result_set ;
			}
			
			for( sqlite3_col_index = 0 ; sqlite3_col_index < sqlite3_col_count ; sqlite3_col_index++ , set_index++ )
			{
				sqlite3_field_value = (char*)sqlite3_column_text( stmt , sqlite3_col_index ) ;
				
				if( sqlite3_column_type( stmt , sqlite3_col_index ) != SQLITE_NULL )
					sqlite3_query_result_set[set_index] = strdup( sqlite3_field_value ) ;
				else
					sqlite3_query_result_set[set_index] = NULL ;
				_TRACE( "    sqlite3_col_index[%lu] : type[%d] buf[%s]" , (unsigned long)sqlite3_col_index , sqlite3_query_field_set[sqlite3_col_index].field_type , sqlite3_field_value )
			}
			
			sqlite3_row_count++;
			
			nret = sqlite3_step( stmt ) ;
			if( nret == SQLITE_DONE )
			{
				break;
			}
			else if( nret != SQLITE_ROW )
			{
				if( sqlite3_query_field_set )
					free( sqlite3_query_field_set );
				sqlite3_finalize( stmt );
				DBCSetLastErrno( CDBC_ERROR_QUERY );
				DBCSetLastNativeErrno( sqlite3_errcode(db_conn->sqlite3_db) );
				DBCSetLastNativeError( (char*)sqlite3_errmsg(db_conn->sqlite3_db) );
				return;
			}
			
			sqlite3_page_row_index++;
		}
		sqlite3_query_field_set[sqlite3_col_count].field_length = sqlite3_row_count ;
		
		sqlite3_affected_count = sqlite3_changes( db_conn->sqlite3_db ) ;
	}
	else
	{
		sqlite3_finalize( stmt );
		DBCSetLastErrno( CDBC_ERROR_QUERY );
		DBCSetLastNativeErrno( sqlite3_errcode(db_conn->sqlite3_db) );
		DBCSetLastNativeError( (char*)sqlite3_errmsg(db_conn->sqlite3_db) );
		return;
	}
	
	sqlite3_finalize( stmt );
	
	if( row_count )
		(*row_count) = sqlite3_row_count ;
	if( col_count )
		(*col_count) = sqlite3_col_count ;
	if( query_field_set )
		(*query_field_set) = sqlite3_query_field_set ;
	if( query_result_set )
		(*query_result_set) = sqlite3_query_result_set ;
	if( affected_count )
		(*affected_count) = sqlite3_affected_count ;
	return;
}

